//////////////////////////////////////////////////////////////////
//
// FaceMesh.cs
//
// Copyright (c) 2012 Dan Pike.
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation files
// (the "Software"), to deal in the Software without restriction,
// including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software,
// and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using System;
using System.Collections.Generic;
using System.Linq;
using Utilities;

namespace FrontEnd
{
   public class FaceMesh : Microsoft.Xna.Framework.Game
   {
      ////////////////////////////////////////////////////////////////////////
      //
      public FaceMesh()
      {
         graphics_ = new GraphicsDeviceManager(this);
         Content.RootDirectory = "FrontEndContent";
      }

      /// <summary>
      /// Allows the game to perform any initialization it needs to before starting to run.
      /// This is where it can query for any required services and load any non-graphic
      /// related content.  Calling base.Initialize will enumerate through any components
      /// and initialize them as well.
      /// </summary>
      protected override void Initialize()
      {
         base.Initialize();
      }

      /// <summary>
      /// LoadContent will be called once per game and is the place to load
      /// all of your content.
      /// </summary>
      protected override void LoadContent()
      {
         // TODO: use this.Content to load your game content here
         Window.Title = "Emily";

         // Load the bitmap
         Texture2D heightMap = Content.Load<Texture2D>("hme");
         Color[] heightMapColors = new Color[heightMap.Height * heightMap.Width];
         heightMap.GetData(heightMapColors);

         float[,] rawHeights = new float[512, 512];
         for (int x = 0; x < 512; ++x)
         {
            for (int y = 0; y < 512; ++y)
            {
               rawHeights[x, y] = (float)heightMapColors[x + y * heightMap.Width].R ;
            }
         }

         this.Window.AllowUserResizing = true;
         this.IsMouseVisible = true;
         
         // Convert it to a mesh
         mesh_ = new Geometry.IdealHeightMap();
         mesh_.needSplitChecker = new Geometry.IdealHeightMap.CheckNeedsSplit(handleNeedSplit);
         mesh_.setHeights(rawHeights, 0, 0, 512, 512);
         mesh_.recalculate(DateTime.MaxValue);

         vertices_ = new List<VertexPositionColor>();
         for (int rootIndex = 0; rootIndex < 2; ++rootIndex)
         {
            Geometry.IdealVertex[] meshVertices = mesh_.getTriangles(rootIndex);
            for (int index = 0; index < meshVertices.Length; ++index)
            {
               float z = meshVertices[index].z;
               Vector3 vector3 = new Vector3(meshVertices[index].x, meshVertices[index].y, z);

               float b = (float)((z < 64) ? z * 4.0 : (z < 128) ? 256 - 4.0 * (z - 128) : 0);
               float g = (float)((z < 64) ? 0 : (z < 128) ? 4.0 * (z - 64) : (z < 192) ? 256 - 4.0 * (z - 128) : 0);
               float r = (float)((z < 64) ? 0 : (z < 128) ? 0 : (z < 192) ? 4.0 * (z - 128) : 256 - 4.0 * (z - 192));
               vertices_.Add(new VertexPositionColor(vector3, new Color(r, g, b)));
            }
         }
         bounds_ = new List<VertexPositionColor>();
         bounds_.Add(new VertexPositionColor(new Vector3(0.0f, 0.0f, 0.0f), Color.Yellow));
         bounds_.Add(new VertexPositionColor(new Vector3(0.0f, 511.0f, 0.0f), Color.Yellow));
         bounds_.Add(new VertexPositionColor(new Vector3(511.0f, 511.0f, 0.0f), Color.Yellow));
         bounds_.Add(new VertexPositionColor(new Vector3(511.0f, 0.0f, 0.0f), Color.Yellow));
         bounds_.Add(new VertexPositionColor(new Vector3(0.0f, 0.0f, 0.0f), Color.Yellow));
         
         graphics_.PreferredBackBufferHeight = 1024;
         graphics_.PreferredBackBufferWidth = 1024;
         effect_ = Content.Load<Effect>("effects");

         // Create the view so that we see the whole mesh

         view_ = Matrix.CreateLookAt(new Vector3(-50.0f, 255.0f, 350.0f), new Vector3(255.0f, 255.0f, 0.0f), Vector3.UnitZ);
         projection_ = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver2, graphics_.GraphicsDevice.Viewport.AspectRatio, 100.0f, 1000.0f);
         world_ = Matrix.CreateTranslation(Vector3.Zero);
      }

      /// <summary>
      /// Return true if we should split because of the error in height
      /// </summary>
      private bool handleNeedSplit(Geometry.IdealTriangle triangle)
      {
         float renderedHeight = (triangle.vertexLeft.z + triangle.vertexRight.z) / 2.0f;
         float trueHeight = mesh_.getHeight((ushort)((triangle.vertexLeft.x + triangle.vertexRight.x) / 2.0f),
            (ushort)((triangle.vertexLeft.y + triangle.vertexRight.y) / 2.0f));
         return 1.0f < Math.Abs(renderedHeight - trueHeight);
      }

      /// <summary>
      /// UnloadContent will be called once per game and is the place to unload
      /// all content.
      /// </summary>
      protected override void UnloadContent()
      {
         // TODO: Unload any non ContentManager content here
      }

      /// <summary>
      /// Allows the game to run logic such as updating the world,
      /// checking for collisions, gathering input, and playing audio.
      /// </summary>
      /// <param name="gameTime">Provides a snapshot of timing values.</param>
      protected override void Update(GameTime gameTime)
      {
         // Allows the game to exit
         if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
            this.Exit();

         MouseState mouseState = Mouse.GetState();
         if (mouseState.LeftButton == ButtonState.Pressed)
         {
            // world_.Translation -= new Vector3(3, 0, 0);
            Matrix rotation = Matrix.CreateRotationZ(0.01f);
            world_ *= rotation;
         }
         else if (mouseState.RightButton == ButtonState.Pressed)
         {
            world_.Translation += new Vector3(3, 0, 0);
         }
         else if (mouseState.XButton1 == ButtonState.Pressed)
         {
            world_.Translation -= new Vector3(0, 3, 0);
         }
         else if (mouseState.XButton2 == ButtonState.Pressed)
         {
            world_.Translation += new Vector3(0, 3, 0);
         }

         mesh_.recalculate(DateTime.MaxValue);

         base.Update(gameTime);
      }

      /// <summary>
      /// This is called when the game should draw itself.
      /// </summary>
      /// <param name="gameTime">Provides a snapshot of timing values.</param>
      protected override void Draw(GameTime gameTime)
      {
         GraphicsDevice device = graphics_.GraphicsDevice;
//         GraphicsDevice.Clear(Color.CornflowerBlue);

         effect_.CurrentTechnique = effect_.Techniques["ColoredNoShading"];
         effect_.Parameters["xView"].SetValue(view_);
         effect_.Parameters["xProjection"].SetValue(projection_);
         effect_.Parameters["xWorld"].SetValue(world_);

         RasterizerState rs = new RasterizerState();
         rs.FillMode = FillMode.WireFrame;
         rs.CullMode = CullMode.CullClockwiseFace;
         device.RasterizerState = rs;

         VertexPositionColor[] bounds = bounds_.ToArray();
         VertexPositionColor[] vertices = vertices_.ToArray();
         foreach (EffectPass pass in effect_.CurrentTechnique.Passes)
         {
            pass.Apply();

            device.DrawUserPrimitives(PrimitiveType.LineStrip, bounds, 0, 4);
            device.DrawUserPrimitives(PrimitiveType.TriangleList, vertices, 0, vertices.Length / 3);
         }
         base.Draw(gameTime);
      }

      ////////////////////////////////////////////////////////////////////////
      //
      private Logger logger_ = new Logger();
      private GraphicsDeviceManager graphics_;
      private Effect effect_;
      private Matrix view_;
      private Matrix projection_;
      private Matrix world_;
      private Geometry.IdealHeightMap mesh_;
      private List<VertexPositionColor> vertices_;
      private List<VertexPositionColor> bounds_;
   }
}
